package com.hzgj.bcl.soa.util;

import com.google.common.base.Charsets;

import com.hzgj.bcl.soa.Address;
import com.hzgj.bcl.soa.rpc.IpAddress;
import com.hzgj.bcl.soa.rpc.message.Message;
import com.hzgj.bcl.soa.spi.MessageHandleFactory;
import com.hzgj.bcl.util.lang.StrKit;

import java.io.DataOutput;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Util {

    public static final IOException REMOTELY_CLOSED_EXCEPTION = new IOException("Remotely closed");
    public static final IOException CHANNEL_CLOSED_EXCEPTION = new IOException("Channel closed");
    public static final IOException REMOTE_EXCEPTION = new IOException("Remote exception");
    // constants
    private static final NumberFormat f;
    private static final Map<Class<? extends Object>, Byte> PRIMITIVE_TYPES = new HashMap<>(15);
    private static final byte TYPE_BOOLEAN = 10;
    private static final byte TYPE_BYTE = 11;
    private static final byte TYPE_CHAR = 12;
    private static final byte TYPE_DOUBLE = 13;
    private static final byte TYPE_FLOAT = 14;
    private static final byte TYPE_INT = 15;
    private static final byte TYPE_LONG = 16;
    private static final byte TYPE_SHORT = 17;
    private static final byte TYPE_STRING = 18;
    private static final byte TYPE_BYTEARRAY = 19;
    private static final Pattern METHOD_NAME_TO_ATTR_NAME_PATTERN = Pattern.compile("[A-Z]+");
    private static final Pattern ATTR_NAME_TO_METHOD_NAME_PATTERN = Pattern.compile("_.");
    /**
     * The max size of an address list, e.g. used in View or Digest when toString() is called. Limiting this
     * reduces the amount of log data
     */
    static boolean resolve_dns = false;

    static {
        /* Trying to get value of resolve_dns. PropertyPermission not granted if
        * running in an untrusted environment with JNLP */
        try {
            resolve_dns = Boolean.valueOf(System.getProperty("resolve.dns", "false"));
        } catch (SecurityException ex) {
            resolve_dns = false;
        }
        f = NumberFormat.getNumberInstance();
        f.setGroupingUsed(false);
        // f.setMinimumFractionDigits(2);
        f.setMaximumFractionDigits(2);

        PRIMITIVE_TYPES.put(Boolean.class, TYPE_BOOLEAN);
        PRIMITIVE_TYPES.put(Byte.class, TYPE_BYTE);
        PRIMITIVE_TYPES.put(Character.class, TYPE_CHAR);
        PRIMITIVE_TYPES.put(Double.class, TYPE_DOUBLE);
        PRIMITIVE_TYPES.put(Float.class, TYPE_FLOAT);
        PRIMITIVE_TYPES.put(Integer.class, TYPE_INT);
        PRIMITIVE_TYPES.put(Long.class, TYPE_LONG);
        PRIMITIVE_TYPES.put(Short.class, TYPE_SHORT);
        PRIMITIVE_TYPES.put(String.class, TYPE_STRING);
        PRIMITIVE_TYPES.put(byte[].class, TYPE_BYTEARRAY);
    }

    public static int getNextHigherPowerOfTwo(int num) {
        if (num <= 0) {
            return 1;
        }
        int highestBit = Integer.highestOneBit(num);
        return num <= highestBit ? highestBit : highestBit << 1;
    }

    public static byte[] createDigest(String passcode, long t1, double q1)
            throws IOException, NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA");
        md.update(passcode.getBytes(Charsets.UTF_8));
        ByteBuffer bb = ByteBuffer.allocate(16); //8 bytes for long and double each
        bb.putLong(t1);
        bb.putDouble(q1);
        md.update(bb);
        return md.digest();
    }

    public static int size(Address addr) {
        int retval = Constants.BYTE_SIZE; // flags
        if (addr == null) {
            return retval;
        }

        if (addr instanceof UUID) {
            Class<? extends Address> clazz = addr.getClass();
            if (clazz.equals(UUID.class)) {
                return retval + addr.size();
            }
        }
        if (addr instanceof IpAddress) {
            return retval + addr.size();
        }

        retval += Constants.SHORT_SIZE; // magic number
        retval += addr.size();
        return retval;
    }

    public static int size(String s) {
        int retval = Constants.BYTE_SIZE;
        if (s != null) {
            retval += s.length() + 2;
        }
        return retval;
    }

    public static int size(byte[] buf) {
        int retval = Constants.BYTE_SIZE + Constants.INT_SIZE;
        if (buf != null) {
            retval += buf.length;
        }
        return retval;
    }

    /**
     * Returns the marshalled size of a Collection of Addresses.
     * <em>Assumes elements are of the same type !</em>
     *
     * @param addrs Collection<Address>
     * @return long size
     */
    public static long size(Collection<? extends Address> addrs) {
        int retval = Constants.SHORT_SIZE; // number of elements
        if (addrs != null && !addrs.isEmpty()) {
            Address addr = addrs.iterator().next();
            retval += size(addr) * addrs.size();
        }
        return retval;
    }

    public static long size(Address[] addrs) {
        int retval = Constants.SHORT_SIZE; // number of elements
        if (addrs != null) {
            for (Address addr : addrs) {
                retval += Util.size(addr);
            }
        }
        return retval;
    }

    public static void writeByteBuffer(byte[] buf, DataOutput out) throws Exception {
        writeByteBuffer(buf, 0, buf.length, out);
    }

    public static void writeByteBuffer(byte[] buf, int offset, int length, DataOutput out) throws Exception {
        if (buf != null) {
            out.write(1);
            out.writeInt(length);
            out.write(buf, offset, length);
        } else {
            out.write(0);
        }
    }

    /**
     * Returns a random value in the range [1 - range]
     */
    public static long random(long range) {
        return (long) ((Math.random() * range) % range) + 1;
    }


    public static String dumpThreads() {
        StringBuilder sb = new StringBuilder();
        ThreadMXBean bean = ManagementFactory.getThreadMXBean();
        long[] ids = bean.getAllThreadIds();
        _printThreads(bean, ids, sb);
        long[] deadlocks = bean.findDeadlockedThreads();
        if (deadlocks != null && deadlocks.length > 0) {
            sb.append("deadlocked threads:\n");
            _printThreads(bean, deadlocks, sb);
        }

        deadlocks = bean.findMonitorDeadlockedThreads();
        if (deadlocks != null && deadlocks.length > 0) {
            sb.append("monitor deadlocked threads:\n");
            _printThreads(bean, deadlocks, sb);
        }
        return sb.toString();
    }


    protected static void _printThreads(ThreadMXBean bean, long[] ids, StringBuilder sb) {
        ThreadInfo[] threads = bean.getThreadInfo(ids, 20);
        for (ThreadInfo info : threads) {
            if (info == null) {
                continue;
            }
            sb.append(info.getThreadName()).append(":\n");
            StackTraceElement[] stack_trace = info.getStackTrace();
            for (StackTraceElement el : stack_trace) {
                sb.append("    at ").append(el.getClassName()).append(".").append(el.getMethodName());
                sb.append("(").append(el.getFileName()).append(":").append(el.getLineNumber()).append(")");
                sb.append("\n");
            }
            sb.append("\n\n");
        }
    }


    public static boolean interruptAndWaitToDie(Thread t) {
        return interruptAndWaitToDie(t, Constants.THREAD_SHUTDOWN_WAIT_TIME);
    }

    public static boolean interruptAndWaitToDie(Thread t, long timeout) {
        if (t == null) {
            throw new IllegalArgumentException("Thread can not be null");
        }
        t.interrupt(); // interrupts the sleep()
        try {
            t.join(timeout);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // set interrupt flag again
        }
        return t.isAlive();
    }

    public static Address[] createRandomAddresses(int num, boolean use_numbers) {
        Address[] addresses = new Address[num];
        int number = 1;
        char c = 'A';
        for (int i = 0; i < addresses.length; i++) {
            addresses[i] = Util.createRandomAddress(use_numbers ? String.valueOf(number++) : String.valueOf(c++));
        }
        return addresses;
    }

    public static Address createRandomAddress(String name) {
        return UUID.randomUUID();
    }

    public static <T> String print(Collection<T> objs) {
        StringBuilder sb = new StringBuilder();
        boolean first = true;
        for (T obj : objs) {
            if (first) {
                first = false;
            } else {
                sb.append(", ");
            }
            sb.append(obj);
        }
        return sb.toString();
    }


    public static <T> String print(Map<T, T> map) {
        StringBuilder sb = new StringBuilder();
        boolean first = true;
        for (Map.Entry<T, T> entry : map.entrySet()) {
            if (first) {
                first = false;
            } else {
                sb.append(", ");
            }
            sb.append(entry.getKey()).append("=").append(entry.getValue());
        }
        return sb.toString();
    }

    public static Field[] getAllDeclaredFields(final Class clazz) {
        return getAllDeclaredFieldsWithAnnotations(clazz);
    }

    public static Field[] getAllDeclaredFieldsWithAnnotations(final Class clazz, Class<? extends Annotation>... annotations) {
        List<Field> list = new ArrayList<>(30);
        for (Class curr = clazz; curr != null; curr = curr.getSuperclass()) {
            Field[] fields = curr.getDeclaredFields();
            if (fields != null) {
                for (Field field : fields) {
                    if (annotations != null && annotations.length > 0) {
                        for (Class<? extends Annotation> annotation : annotations) {
                            if (field.isAnnotationPresent(annotation)) {
                                list.add(field);
                            }
                        }
                    } else {
                        list.add(field);
                    }
                }
            }
        }

        Field[] retval = new Field[list.size()];
        for (int i = 0; i < list.size(); i++) {
            retval[i] = list.get(i);
        }
        return retval;
    }

    public static Method[] getAllDeclaredMethodsWithAnnotations(final Class clazz, Class<? extends Annotation>... annotations) {
        List<Method> list = new ArrayList<>(30);
        for (Class curr = clazz; curr != null; curr = curr.getSuperclass()) {
            Method[] methods = curr.getDeclaredMethods();
            if (methods != null) {
                for (Method method : methods) {
                    if (annotations != null && annotations.length > 0) {
                        for (Class<? extends Annotation> annotation : annotations) {
                            if (method.isAnnotationPresent(annotation)) {
                                list.add(method);
                            }
                        }
                    } else {
                        list.add(method);
                    }
                }
            }
        }

        Method[] retval = new Method[list.size()];
        for (int i = 0; i < list.size(); i++) {
            retval[i] = list.get(i);
        }
        return retval;
    }


    public static <A extends Annotation> A getAnnotation(Class<?> clazz, Class<A> annotationClass) {
        for (Class<?> curr = clazz; curr != null; curr = curr.getSuperclass()) {
            A ann = curr.getAnnotation(annotationClass);
            if (ann != null) {
                return ann;
            }
        }
        return null;
    }

    public static Field getField(final Class clazz, String field_name) {
        if (clazz == null || field_name == null) {
            return null;
        }

        Field field = null;
        for (Class curr = clazz; curr != null; curr = curr.getSuperclass()) {
            try {
                return curr.getDeclaredField(field_name);
            } catch (NoSuchFieldException e) {
            }
        }
        return field;
    }

    public static void setField(Field field, Object target, Object value) {
        if (!Modifier.isPublic(field.getModifiers())) {
            field.setAccessible(true);
        }
        try {
            field.set(target, value);
        } catch (IllegalAccessException iae) {
            throw new IllegalArgumentException("Could not set field " + field, iae);
        }
    }

    public static Object getField(Field field, Object target) {
        if (!Modifier.isPublic(field.getModifiers())) {
            field.setAccessible(true);
        }
        try {
            return field.get(target);
        } catch (IllegalAccessException iae) {
            throw new IllegalArgumentException("Could not get field " + field, iae);
        }
    }


    public static Field findField(Object target, List<String> possible_names) {
        if (target == null) {
            return null;
        }
        for (Class<?> clazz = target.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
            for (String name : possible_names) {
                try {
                    return clazz.getDeclaredField(name);
                } catch (Exception e) {
                }
            }
        }
        return null;
    }

    public static Method findMethod(Class<?> root_class, List<String> possible_names, Class<?>... parameter_types) {
        if (root_class == null) {
            return null;
        }
        for (Class<?> clazz = root_class; clazz != null; clazz = clazz.getSuperclass()) {
            for (String name : possible_names) {
                try {
                    return clazz.getDeclaredMethod(name, parameter_types);
                } catch (Exception e) {
                }
            }
        }
        return null;
    }

    /**
     * e.g. "bela,jeannette,michelle" --> List{"bela", "jeannette", "michelle"}
     */
    public static List<String> parseCommaDelimitedStrings(String l) {
        return parseStringList(l, ",");
    }

    public static List<String> parseStringList(String l, String separator) {
        List<String> tmp = new LinkedList<>();
        StringTokenizer tok = new StringTokenizer(l, separator);
        String t;

        while (tok.hasMoreTokens()) {
            t = tok.nextToken();
            tmp.add(t.trim());
        }

        return tmp;
    }

    /**
     * @param s
     * @return List<NetworkInterface>
     */
    public static List<NetworkInterface> parseInterfaceList(String s) throws Exception {
        List<NetworkInterface> interfaces = new ArrayList<>(10);
        if (s == null) {
            return null;
        }

        StringTokenizer tok = new StringTokenizer(s, ",");
        String interface_name;
        NetworkInterface intf;

        while (tok.hasMoreTokens()) {
            interface_name = tok.nextToken();

            // try by name first (e.g. (eth0")
            intf = NetworkInterface.getByName(interface_name);

            // next try by IP address or symbolic name
            if (intf == null) {
                intf = NetworkInterface.getByInetAddress(InetAddress.getByName(interface_name));
            }

            if (intf == null) {
                throw new Exception("interface " + interface_name + " not found");
            }
            if (!interfaces.contains(intf)) {
                interfaces.add(intf);
            }
        }
        return interfaces;
    }

    public static String print(List<NetworkInterface> interfaces) {
        StringBuilder sb = new StringBuilder();
        boolean first = true;

        for (NetworkInterface intf : interfaces) {
            if (first) {
                first = false;
            } else {
                sb.append(", ");
            }
            sb.append(intf.getName());
        }
        return sb.toString();
    }


    public static String shortName(String hostname) {
        if (hostname == null) {
            return null;
        }

        int index = hostname.indexOf('.');
        if (index > 0 && !Character.isDigit(hostname.charAt(0))) {
            return hostname.substring(0, index);
        } else {
            return hostname;
        }
    }

    public static String generateLocalName() {
        String retval = null;
        try {
            retval = shortName(InetAddress.getLocalHost().getHostName());
        } catch (Throwable e) {
        }
        if (retval == null) {
            try {
                retval = shortName(InetAddress.getByName(null).getHostName());
            } catch (Throwable e) {
                retval = "localhost";
            }
        }

        long counter = Util.random(Short.MAX_VALUE * 2);
        return retval + "-" + counter;
    }


    /**
     * Finds a network interface of sub-interface with the given name
     */
    public static NetworkInterface getByName(String name) throws SocketException {
        if (name == null) {
            return null;
        }
        Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
        while (en.hasMoreElements()) {
            NetworkInterface intf = en.nextElement();
            if (intf.getName().equals(name)) {
                return intf;
            }
            Enumeration<NetworkInterface> en2 = intf.getSubInterfaces();
            while (en2.hasMoreElements()) {
                NetworkInterface intf2 = en2.nextElement();
                if (intf2.getName().equals(name)) {
                    return intf2;
                }
            }
        }
        return null;
    }


    public static boolean checkForLinux() {
        return checkForPresence("os.name", "linux");
    }

    public static boolean checkForHp() {
        return checkForPresence("os.name", "hp");
    }

    public static boolean checkForSolaris() {
        return checkForPresence("os.name", "sun");
    }

    public static boolean checkForWindows() {
        return checkForPresence("os.name", "win");
    }

    public static boolean checkForAndroid() {
        return contains("java.vm.vendor", "android");
    }

    public static boolean checkForMac() {
        return checkForPresence("os.name", "mac");
    }

    private static boolean checkForPresence(String key, String value) {
        try {
            String tmp = System.getProperty(key);
            return tmp != null && tmp.trim().toLowerCase().startsWith(value);
        } catch (Throwable t) {
            return false;
        }
    }

    private static boolean contains(String key, String value) {
        try {
            String tmp = System.getProperty(key);
            return tmp != null && tmp.trim().toLowerCase().contains(value.trim().toLowerCase());
        } catch (Throwable t) {
            return false;
        }
    }

    public static List<NetworkInterface> getAllAvailableInterfaces() throws SocketException {
        List<NetworkInterface> retval = new ArrayList<>(10);
        NetworkInterface intf;
        for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
            intf = (NetworkInterface) en.nextElement();
            retval.add(intf);
        }
        return retval;
    }

    public static Collection<InetAddress> getAllAvailableAddresses() {
        Set<InetAddress> retval = new HashSet<>();
        Enumeration en;

        try {
            en = NetworkInterface.getNetworkInterfaces();
            if (en == null) {
                return retval;
            }
            while (en.hasMoreElements()) {
                NetworkInterface intf = (NetworkInterface) en.nextElement();
                Enumeration<InetAddress> addrs = intf.getInetAddresses();
                while (addrs.hasMoreElements()) {
                    retval.add(addrs.nextElement());
                }
            }
        } catch (SocketException e) {
            e.printStackTrace();
        }

        return retval;
    }

    /**
     * Returns a value associated wither with one or more system properties, or found in the props map
     *
     * @param system_props
     * @param props         List of properties read from the configuration file
     * @param prop_name     The name of the property, will be removed from props if found
     * @param default_value Used to return a default value if the properties or system properties didn't have the value
     * @return The value, or null if not found
     */
    public static String getProperty(String[] system_props, Properties props, String prop_name, String default_value) {
        String retval = null;
        if (props != null && prop_name != null) {
            retval = props.getProperty(prop_name);
            props.remove(prop_name);
        }

        String tmp, prop;
        if (retval == null && system_props != null) {
            for (int i = 0; i < system_props.length; i++) {
                prop = system_props[i];
                if (prop != null) {
                    try {
                        tmp = System.getProperty(prop);
                        if (tmp != null) {
                            return tmp;
                        }
                    } catch (SecurityException ex) {
                    }
                }
            }
        }

        if (retval == null) {
            return default_value;
        }
        return retval;
    }

    /**
     * Parses a var which might be comma delimited, e.g. bla,foo:1000: if 'bla' is set, return its value. Else,
     * if 'foo' is set, return its value, else return "1000"
     *
     * @param var
     * @param default_value
     * @return
     */
    private static String _getProperty(String var, String default_value) {
        if (var == null) {
            return null;
        }
        List<String> list = parseCommaDelimitedStrings(var);
        if (list == null || list.isEmpty()) {
            list = new ArrayList<>(1);
            list.add(var);
        }
        String retval = null;
        for (String prop : list) {
            try {
                retval = System.getProperty(prop);
                if (retval != null) {
                    return retval;
                }
            } catch (Throwable e) {
            }
        }
        return default_value;
    }


    /**
     * Used to convert a byte array in to a java.lang.String object
     *
     * @param bytes the bytes to be converted
     * @return the String representation
     */
    private static String getString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            byte b = bytes[i];
            sb.append(0x00FF & b);
            if (i + 1 < bytes.length) {
                sb.append("-");
            }
        }
        return sb.toString();
    }


    /**
     * Converts a java.lang.String in to a MD5 hashed String
     *
     * @param source the source String
     * @return the MD5 hashed version of the string
     */
    public static String md5(String source) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] bytes = md.digest(source.getBytes(Charsets.UTF_8));
            return getString(bytes);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Converts a java.lang.String in to a SHA hashed String
     *
     * @param source the source String
     * @return the MD5 hashed version of the string
     */
    public static String sha(String source) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA");
            byte[] bytes = md.digest(source.getBytes(Charsets.UTF_8));
            return getString(bytes);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Converts a method name to an attribute name, e.g. getFooBar() --> foo_bar, isFlag --> flag.
     *
     * @param methodName
     * @return
     */
    public static String methodNameToAttributeName(final String methodName) {
        String name = methodName;
        if ((methodName.startsWith("get") || methodName.startsWith("set")) && methodName.length() > 3) {
            name = methodName.substring(3);
        } else if (methodName.startsWith("is") && methodName.length() > 2) {
            name = methodName.substring(2);
        }

        // Pattern p=Pattern.compile("[A-Z]+");
        Matcher m = METHOD_NAME_TO_ATTR_NAME_PATTERN.matcher(name);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            int start = m.start(), end = m.end();
            String str = name.substring(start, end).toLowerCase();
            if (str.length() > 1) {
                String tmp1 = str.substring(0, str.length() - 1);
                String tmp2 = str.substring(str.length() - 1);
                str = tmp1 + "_" + tmp2;
            }
            if (start == 0) {
                m.appendReplacement(sb, str);
            } else {
                m.appendReplacement(sb, "_" + str);
            }
        }
        m.appendTail(sb);
        return sb.length() > 0 ? sb.toString() : methodName;
    }

    /**
     * Converts a method name to a Java attribute name, e.g. getFooBar() --> fooBar, isFlag --> flag.
     *
     * @param methodName
     * @return
     */
    public static String methodNameToJavaAttributeName(final String methodName) {
        String name = methodName;
        if ((methodName.startsWith("get") || methodName.startsWith("set")) && methodName.length() > 3) {
            name = methodName.substring(3);
        } else if (methodName.startsWith("is") && methodName.length() > 2) {
            name = methodName.substring(2);
        }

        if (Character.isUpperCase(name.charAt(0))) {
            return name.substring(0, 1).toLowerCase() + name.substring(1);
        }
        return name;
    }


    public static String attributeNameToMethodName(String attr_name) {
        if (attr_name.contains("_")) {
            // Pattern p=Pattern.compile("_.");
            Matcher m = ATTR_NAME_TO_METHOD_NAME_PATTERN.matcher(attr_name);
            StringBuffer sb = new StringBuffer();
            while (m.find()) {
                m.appendReplacement(sb, attr_name.substring(m.end() - 1, m.end()).toUpperCase());
            }
            m.appendTail(sb);
            char first = sb.charAt(0);
            if (Character.isLowerCase(first)) {
                sb.setCharAt(0, Character.toUpperCase(first));
            }
            return sb.toString();
        } else {
            if (Character.isLowerCase(attr_name.charAt(0))) {
                return attr_name.substring(0, 1).toUpperCase() + attr_name.substring(1);
            } else {
                return attr_name;
            }
        }
    }

    public static long millisTime() {
        return System.nanoTime() / 1000000;
    }

    public static Message buildThrowableMessage(Throwable throwable, Map<String, Object> headers, String protocol) {
        MessageHandleFactory messageHandleFactory = ServiceLoaderUtil.getMessageBuildFactory(protocol);
        return messageHandleFactory.buildErrorResponse(throwable, headers);
    }

    public static IOException buildStaticException(String message) {
        IOException ioe = new IOException(message);
        ioe.setStackTrace(new StackTraceElement[]{});
        return ioe;
    }

    public static String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder("");
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
            stringBuilder.append(" ");
        }
        return stringBuilder.toString();
    }

    public static String findSetter(String attr_name) {
        final String name = Util.attributeNameToMethodName(attr_name);
        return "set" + name;
    }

    public static Method findSetter(Class<?> clazz, String attr_name) {
        final String name = Util.attributeNameToMethodName(attr_name);
        final String fluent_name = StrKit.firstCharToLowerCase(name);
        Field field = Util.getField(clazz, attr_name);
        Class<?> field_type = field != null ? field.getType() : null;
        String setter_name = "set" + name;

        if (field_type != null) {
            Method method = findMethod(clazz, Arrays.asList(fluent_name, setter_name), field_type);
            if (method != null && isSetMethod(method)) {
                return method;
            }
        }
        throw new RuntimeException("can't find method " + setter_name);
    }

    public static boolean isSetMethod(Method method) {
        return method.getParameterTypes().length == 1;
    }

}